﻿using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleService
{
    public class ConsoleParser
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        private String _filename;
        private int _currentpos;
        private DateTime _createtime;
        private Instance _instance;
        private Thread _thread;
        private List<TempPlayer> _playerQueue;

        private class TempPlayer : Player
        {
            public int connection_id { get; set; }
            public int log_pos { get; set; }
            public DateTime added_time { get; set; }

            public Player getPlayer()
            {
                return new Player()
                {
                    gameid = this.gameid,
                    guid = this.guid,
                    last_datetime = DateTime.Now,
                    last_ip = this.last_ip,
                    last_server = this.last_server,
                    player_name = this.player_name,
                };
            }
        }

        public DateTime LastUpdate { get; internal set; }
        public Boolean CanRun { get; set; }
        public Boolean EndOfFile { get; set; }

        public delegate void PlayerConnectedDelegate(Player player);
        public event PlayerConnectedDelegate PlayerConnected;
        
        public ConsoleParser(Instance instance)
        {
            this.CanRun = true;
            this.EndOfFile = false;
            this._instance = instance;
            this._filename = "console.log";
            if (instance != null && instance.process != null && instance.process.Id > 0)
            {
                this._filename = string.Format(System.Configuration.ConfigurationManager.AppSettings.Get("LogFileName"), instance.process.Id);
                this._filename = System.IO.Path.Combine(this._instance.server.config_path, this._filename);
                logger.Info("File name set to {0} for server {1}", this._filename, this._instance.server.server_id);
            };
            this._playerQueue = new List<TempPlayer>();
            this._thread = new Thread(new ThreadStart(Run));
            this._currentpos = 0;
            this._createtime = DateTime.MinValue;
            _thread.Start();
        }

        public void Abort()
        {
            this._thread.Abort();
        }

        private void Run()
        {
            // Regex for extraction:
            // BattlEye Server: Player #0 [DZY]SweaZ (82.245.69.227:2304) connected
            // Player [DZY]SweaZ connected (id=76561198067397085).
            // BattlEye Server: Verified GUID (760a057b9c97354aba9837ccbe9e4e13) of player #0 [DZY]SweaZ

            var patternPlayerNameAndIP = @"BattlEye Server: Player #([0-9]{1,3}) (.*) \(([0-9\.:]*)\)";
            var patternPlayerGameId = @"Player (.*) connected \(id=([0-9a-zA-Z]*)\).";
            var patternPlayerGuid = @"BattlEye Server: Verified GUID \(([0-9a-zA-Z]*)\) of player #([0-9]{1,3}) (.*)";

            System.Text.RegularExpressions.MatchCollection matches;
            logger.Info("Console Parser started for server {0} file: {1}", this._instance.server.server_id, this._filename);
            while (this.CanRun)
            {
                try
                {
                    if (this._instance != null && this._instance.process != null && this._instance.process.Id > 0)
                    {
                        var oldname = this._filename;
                        this._filename = string.Format(System.Configuration.ConfigurationManager.AppSettings.Get("LogFileName"), this._instance.process.Id);
                        this._filename = System.IO.Path.Combine(this._instance.server.config_path, this._filename);
                        if (oldname != this._filename)
                        {
                            logger.Info("File name set to {0} for server {1}", this._filename, this._instance.server.server_id);
                        };
                    };
                    if (this._instance.server != null && (this._instance.server.is_started || this._instance.server.is_stopping) && System.IO.File.Exists(this._filename))
                    {
                        try
                        {
                            var createTime = System.IO.File.GetCreationTime(this._filename);
                            if (createTime.CompareTo(this._createtime) > 0)
                            {
                                this._currentpos = 0;
                                this._playerQueue.Clear();
                                this._createtime = createTime;
                            }
                            string content = null;
                            using (var fs = System.IO.File.Open(this._filename, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite))
                            using (var rdr = new System.IO.StreamReader(fs))
                            {
                                int readLength = (int)fs.Length;
                                if (fs.Length >= this._currentpos)
                                {
                                    logger.Trace("Start parsing file for server {0} ({1}/{2})", this._instance.server.server_id, fs.Length, this._currentpos);
                                    readLength = (int)fs.Length - this._currentpos;
                                    if (this._currentpos > 0 && fs.Position != this._currentpos)
                                    {
                                        if (fs.CanSeek)
                                        {
                                            fs.Seek(this._currentpos, System.IO.SeekOrigin.Begin);
                                        }
                                        else
                                        {
                                            fs.Read(new byte[this._currentpos], 0, this._currentpos);
                                        }
                                    }
                                }
                                else
                                {
                                    logger.Debug("File size is smaller than current pos for server {0} ({1}/{2})", this._instance.server.server_id, fs.Length, this._currentpos);
                                    this._currentpos = 0;
                                }
                                logger.Trace("Start reading at pos {0} for server {1} with length {2}", fs.Position, this._instance.server.server_id, readLength);
                                content = rdr.ReadToEnd();
                                logger.Trace("Ended reading at position {0} for server {1} with length {2}", fs.Position, this._instance.server.server_id, content.Length);
                            }
                            this.EndOfFile = string.IsNullOrWhiteSpace(content) || content.Length <= 0;
                            if (!string.IsNullOrWhiteSpace(content))
                            {
                                var foundLine = content.Length > 0;
                                while (foundLine)
                                {
                                    var pos = content.IndexOf(Environment.NewLine);
                                    if (pos >= 0)
                                    {
                                        foundLine = true;
                                        var line = content.Substring(0, pos);
                                        this._currentpos += pos + Environment.NewLine.Length;
                                        if (content.Length > (pos + Environment.NewLine.Length))
                                        {
                                            content = content.Substring(pos + Environment.NewLine.Length);
                                        }
                                        else
                                        {
                                            content = string.Empty;
                                        }


                                        matches = System.Text.RegularExpressions.Regex.Matches(line, patternPlayerNameAndIP);
                                        if (matches.Count > 0 && matches[0].Groups.Count >= 4)
                                        {
                                            var newplayer = new TempPlayer()
                                            {
                                                last_server = this._instance.server.server_id,
                                                connection_id = int.Parse(matches[0].Groups[1].Value),
                                                player_name = matches[0].Groups[2].Value,
                                                last_ip = matches[0].Groups[3].Value,
                                                log_pos = this._currentpos,
                                                added_time = DateTime.Now,
                                            };
                                            if (!this._playerQueue.Any(tp => tp.last_ip == newplayer.last_ip && tp.player_name == tp.player_name ))
                                            {
                                                this._playerQueue.Add(newplayer);
                                                logger.Debug("Player {0}: Player added to the list ({1})", newplayer.connection_id, newplayer.player_name);
                                            } else {
                                                // TODO
                                                logger.Debug("Player {0}: Player already present in the list ({1})", newplayer.connection_id, newplayer.player_name);
                                            }
                                        }

                                        matches = System.Text.RegularExpressions.Regex.Matches(line, patternPlayerGameId);
                                        if (matches.Count > 0 && matches[0].Groups.Count >= 3)
                                        {
                                            var name = matches[0].Groups[1].Value;
                                            var gameid = matches[0].Groups[2].Value;
                                            if (this._playerQueue.Any(tp => tp.player_name == name))
                                            {
                                                var player = this._playerQueue.First(tp => tp.player_name == name);
                                                player.gameid = gameid;
                                                logger.Debug("Player {0}: Player game id found ({1} | {2})", player.connection_id, player.player_name, gameid);
                                            }
                                            else
                                            {
                                                // TODO
                                                logger.Debug("Player: Player not found for game id ({0} | {1})", name, gameid);
                                            }
                                        }

                                        matches = System.Text.RegularExpressions.Regex.Matches(line, patternPlayerGuid);
                                        if (matches.Count > 0 && matches[0].Groups.Count >= 4)
                                        {
                                            var guid = matches[0].Groups[1].Value;
                                            var connection_id = int.Parse(matches[0].Groups[2].Value);
                                            var name = matches[0].Groups[3].Value;
                                            if (this._playerQueue.Any(tp => tp.player_name == name && tp.connection_id == connection_id))
                                            {
                                                var player = this._playerQueue.First(tp => tp.player_name == name && tp.connection_id == connection_id);
                                                player.guid = guid;
                                                logger.Debug("Player {0}: Player guid found ({1} | {2})", player.connection_id, player.player_name, guid);
                                            }
                                            else
                                            {
                                                // TODO
                                                logger.Debug("Player {0}: Player not found for guid ({1} | {2})", connection_id, name, guid);
                                            }
                                        }

                                    }
                                    else
                                    {
                                        foundLine = false;
                                    }
                                }
                                logger.Trace("Ended processing content at position {0} for server {1} with length {2}", this._currentpos, this._instance.server.server_id, content.Length);
                            }
                        }
                        catch (Exception ex)
                        {
                            logger.Error("Error on processing logs for server {0}: {1}", this._instance.server.server_id, ex.Message);
                        }
                    }
                    else
                    {
                        logger.Trace("Skipped reading console file: {0} | {1} | {2}", this._instance.server != null, this._instance.server != null && (this._instance.server.is_started || this._instance.server.is_stopping), System.IO.File.Exists(this._filename));
                    }

                    if (this._playerQueue.Count > 0 && this.PlayerConnected != null)
                    {
                        try
                        {
                            for (int i = this._playerQueue.Count - 1; i >= 0; i-- )
                            {
                                var player = this._playerQueue[i];
                                if (!string.IsNullOrWhiteSpace(player.guid))
                                {
                                    this._playerQueue.RemoveAt(i);
                                    this.PlayerConnected.Invoke(player.getPlayer());
                                    logger.Debug("Player {0}: Player Connected: {1}", player.connection_id, player.guid);
                                }
                                else if (DateTime.Now.Subtract(player.added_time).TotalMinutes > 10)
                                {
                                    this._playerQueue.RemoveAt(i);
                                    logger.Debug("Player {0}: Removed because of timeout", player.connection_id);
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            logger.Error("Error on processing queue for server {0}: {1}", this._instance.server.server_id, ex.Message);
                        }
                    }
                    LastUpdate = DateTime.Now;
                }
                catch (Exception ex)
                {
                    logger.Error("Error on processing run: {0}", ex.Message);
                }
                System.Threading.Thread.Sleep(1000);
            }
            logger.Info("Console parser stopped for server {0}: {1}", this._instance.server.server_id);
        }
    }
}
